package com.weilus.config.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.util.stream.Stream;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    public static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    @Autowired
    ValidImageCodeFilter validImageCodeFilter;
    @Autowired
    SecurityMetadataSourcePropertity propertity;
    @Autowired
    AuthenticationTokenFilter tokenFilter;
    @Autowired
    AuthenticationSuccessHandler successHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        //权限配置
        security(http);

        //登录配置
        http
                .formLogin()
                    .loginProcessingUrl("/login")
                    .loginPage("/login.html")
                    .failureUrl("/login.html?err=1")
                    .permitAll()
                    .successHandler(successHandler)
                    .and()
                .logout()
                    .logoutSuccessUrl("/login.html")
                    .logoutUrl("/logout")
                    .permitAll();

          //基于session 一个账号在服务端只能登录一次
//        http.sessionManagement().maximumSessions(1).expiredUrl("/common/second-login.html");

        // 基于token，所以不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //登录失败或无权限处理
        http.exceptionHandling()
                    .authenticationEntryPoint(authenticationEntryPoint())
                    .accessDeniedHandler(accessDeniedHandler());

        //登录验证码处理
        http.addFilterBefore(validImageCodeFilter, UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(tokenFilter,UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        if(!CollectionUtils.isEmpty(propertity.getNoCheckToken())) {
            propertity.getNoCheckToken().forEach(s -> {
                HttpMethod httpmethod = null;
                String pattern = s;
                if (s.indexOf(" ") != -1) {
                    String[] arr = StringUtils.delimitedListToStringArray(s, " ");
                    httpmethod = HttpMethod.valueOf(arr[0]);
                    pattern = arr[1];
                }
                web.ignoring().mvcMatchers().antMatchers(httpmethod, pattern);
            });
        }
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint(){
        return (request,response,authenticationException)->{
            logger.error("{} -> {}",request.getRequestURL(),authenticationException.getMessage());
            boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
            if(isAjax){
                PrintWriter pw = response.getWriter();
                pw.write("TO_LOGIN");
                pw.flush();
            }else {
                logger.info("重定向 -> /login.html");
                response.sendRedirect("/login.html");
            }
        };
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler(){
        return ((request, response, accessDeniedException) -> {
            logger.error(accessDeniedException.getMessage());
            boolean isAjax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
            if(isAjax){
                PrintWriter pw = response.getWriter();
                pw.write("NOPERMISSION");
                pw.flush();
            }else {
                response.setContentType("text/html;charset=UTF-8");
                PrintWriter pw = response.getWriter();
                pw.write("<html>无权限访问!</html>");
                pw.flush();
            }
        });
    }

    @Autowired
    DataSource dataSource;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery(JdbcDaoImpl.DEF_USERS_BY_USERNAME_QUERY)
                .authoritiesByUsernameQuery(JdbcDaoImpl.DEF_AUTHORITIES_BY_USERNAME_QUERY)
                .groupAuthoritiesByUsername(JdbcDaoImpl.DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY);

    }

    private void security(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests()
                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll();
        if(!CollectionUtils.isEmpty(propertity.getRules())){
            propertity.getRules().entrySet().forEach(e->
                Stream.of(e.getValue()).forEach(ptn -> {
                    String[] arr = ptn.trim().split(" ");
                    String pattern = arr.length == 1 ? arr[0] : arr[1];
                    HttpMethod httpMethod = arr.length == 1 ? null : HttpMethod.valueOf(arr[0]);
                    logger.info("鉴权 [{} {}] -> {}",
                            com.weilus.util.StringUtils.flushLeft(' ',10,httpMethod != null ? httpMethod.name() : "*"),
                            com.weilus.util.StringUtils.flushLeft(' ',25,pattern), e.getKey());
                    registry.antMatchers(httpMethod,pattern).hasAnyAuthority(e.getKey());
                })
            );
        }
        registry.anyRequest().authenticated();
    }
}
